🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / connections / src / commonMain / kotlin / org / meshtastic / feature / connections / domain / usecase / TcpDiscoveryHelpers.kt
Displaying Raw • Download
feature/connections/src/commonMain/kotlin/org/meshtastic/feature/connections/domain/usecase/TcpDiscoveryHelpers.kt bd2863243bab6eb213401d949839a2bc74dde7e2 (bd286324) Text, 6.43 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.connections.domain.usecase
Tff7b72import T7ee787org.meshtastic.core.common.database.DatabaseManager
Tff7b72import T7ee787org.meshtastic.core.common.util.safeCatchingAll
Tff7b72import T7ee787org.meshtastic.core.datastore.model.RecentAddress
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.network.repository.DiscoveredService
Tff7b72import T7ee787org.meshtastic.core.network.repository.NetworkRepository.Companion.toAddressString
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.demo_mode
Tff7b72import T7ee787org.meshtastic.core.resources.demo_mode_replay
Tff7b72import T7ee787org.meshtastic.core.resources.getStringSuspend
Tff7b72import T7ee787org.meshtastic.feature.connections.model.DeviceListEntry
Tff7b72private Tff7b72const Tff7b72val Te6edf3SUFFIX_LENGTH Tff7b72= T79c0ff4
T8b949e/**
* Shared helpers for TCP device discovery logic used by both [CommonGetDiscoveredDevicesUseCase] and the
* Android-specific variant.
*/
T8b949e/** Converts a list of [DiscoveredService] into [DeviceListEntry.Tcp] with display names derived from TXT records. */
Tff7b72internal Tff7b72fun Td2a8ffprocessTcpServicesTb4b4b4(
Te6edf3tcpServicesTb4b4b4: Te6edf3ListTff7b72<Te6edf3DiscoveredServiceTff7b72>Tb4b4b4,
Te6edf3recentAddressesTb4b4b4: Te6edf3ListTff7b72<Te6edf3RecentAddressTff7b72>Tb4b4b4,
Te6edf3defaultShortNameTb4b4b4: Tffa657String Tff7b72= Ta5d6ff"Ta5d6ffMeshtasticTa5d6ff"Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTff7b72> Tb4b4b4{
Tff7b72val Te6edf3recentMap Tff7b72= Te6edf3recentAddressesTb4b4b4.Te6edf3associateByTb4b4b4(Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3address Tb4b4b4}Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3name Tb4b4b4}
Tff7b72return Te6edf3tcpServices
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3service Tff7b72-Tff7b72>
Tff7b72val Te6edf3address Tff7b72= Ta5d6ff"Ta5d6fftTffd700${Te6edf3serviceTb4b4b4.Te6edf3toAddressStringTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"
Tff7b72val Te6edf3txtRecords Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3txt
Tff7b72val Te6edf3shortNameBytes Tff7b72= Te6edf3txtRecordsTff7b72[Ta5d6ff"Ta5d6ffshortnameTa5d6ff"Tff7b72]
Tff7b72val Te6edf3idBytes Tff7b72= Te6edf3txtRecordsTff7b72[Ta5d6ff"Ta5d6ffidTa5d6ff"Tff7b72]
Tff7b72val Te6edf3shortName Tff7b72= Te6edf3shortNameBytesTff7b72?.Te6edf3decodeToStringTb4b4b4(Tb4b4b4) Tff7b72?: Te6edf3defaultShortName
Tff7b72val Te6edf3deviceId Tff7b72= Te6edf3idBytesTff7b72?.Te6edf3decodeToStringTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff!Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72var Te6edf3displayName Tff7b72= Te6edf3recentMapTff7b72[Te6edf3addressTff7b72] Tff7b72?: Te6edf3shortName
Tff7b72if Tb4b4b4(Te6edf3deviceId Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3displayNameTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff"Ta5d6ff_Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3none Tb4b4b4{ Tffa657it Tff7b72=Tff7b72= Te6edf3deviceId Tb4b4b4}Tb4b4b4) Tb4b4b4{
Te6edf3displayName Tff7b72+Tff7b72= Ta5d6ff"Ta5d6ff_Tffd700$Te6edf3deviceIdTa5d6ff"
Tb4b4b4}
Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTb4b4b4(Te6edf3displayNameTb4b4b4, Te6edf3addressTb4b4b4)
Tb4b4b4}
T8b949e// mDNS can resolve two services to one host:port (e.g. re-announce before the old record expires). The device
T8b949e// list keys on fullAddress, and duplicate LazyColumn keys are a hard crash.
Tb4b4b4.Te6edf3distinctBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3fullAddress Tb4b4b4}
Tb4b4b4.Te6edf3sortedBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3name Tb4b4b4}
Tb4b4b4}
T8b949e/** Matches each discovered TCP entry to a [Node] from the database using its mDNS device ID. */
Tff7b72internal Tff7b72fun Td2a8ffmatchDiscoveredTcpNodesTb4b4b4(
Te6edf3entriesTb4b4b4: Te6edf3ListTff7b72<Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTff7b72>Tb4b4b4,
Te6edf3nodeDbTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3resolvedServicesTb4b4b4: Te6edf3ListTff7b72<Te6edf3DiscoveredServiceTff7b72>Tb4b4b4,
Te6edf3databaseManagerTb4b4b4: Te6edf3DatabaseManagerTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTff7b72> Tff7b72= Te6edf3entriesTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3entry Tff7b72-Tff7b72>
Tff7b72val Te6edf3matchingNode Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3databaseManagerTb4b4b4.Te6edf3hasDatabaseForTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3resolvedService Tff7b72= Te6edf3resolvedServicesTb4b4b4.Te6edf3find Tb4b4b4{ Ta5d6ff"Ta5d6fftTffd700${Tffa657itTb4b4b4.Te6edf3toAddressStringTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff" Tff7b72=Tff7b72= Te6edf3entryTb4b4b4.Te6edf3fullAddress Tb4b4b4}
Tff7b72val Te6edf3deviceId Tff7b72= Te6edf3resolvedServiceTff7b72?.Te6edf3txtTff7b72?.Te6edf3getTb4b4b4(Ta5d6ff"Ta5d6ffidTa5d6ff"Tb4b4b4)Tff7b72?.Te6edf3decodeToStringTb4b4b4(Tb4b4b4)
Te6edf3nodeDbTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3find Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3deviceId Tff7b72|Tff7b72| Tb4b4b4(Te6edf3deviceId Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Ta5d6ff"Ta5d6ff!Tffd700$Te6edf3deviceIdTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Te6edf3entryTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3node Tff7b72= Te6edf3matchingNodeTb4b4b4)
Tb4b4b4}
T8b949e/**
* Builds the "recent TCP devices" list by filtering out currently discovered addresses and matching each entry to a
* [Node] by name suffix.
*/
Tff7b72internal Tff7b72fun Td2a8ffbuildRecentTcpEntriesTb4b4b4(
Te6edf3recentAddressesTb4b4b4: Te6edf3ListTff7b72<Te6edf3RecentAddressTff7b72>Tb4b4b4,
Te6edf3discoveredAddressesTb4b4b4: Te6edf3SetTff7b72<Tffa657StringTff7b72>Tb4b4b4,
Te6edf3nodeDbTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3databaseManagerTb4b4b4: Te6edf3DatabaseManagerTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTff7b72> Tff7b72= Te6edf3recentAddresses
Tb4b4b4.Te6edf3filterNot Tb4b4b4{ Te6edf3discoveredAddressesTb4b4b4.Te6edf3containsTb4b4b4(Tffa657itTb4b4b4.Te6edf3addressTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3DeviceListEntryTb4b4b4.Te6edf3TcpTb4b4b4(Tffa657itTb4b4b4.Te6edf3nameTb4b4b4, Tffa657itTb4b4b4.Te6edf3addressTb4b4b4) Tb4b4b4}
T8b949e// The persisted list is not guaranteed unique: only add() dedupes, and legacy blobs bypass it. The device list
T8b949e// keys on fullAddress, and duplicate LazyColumn keys are a hard crash.
Tb4b4b4.Te6edf3distinctBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3fullAddress Tb4b4b4}
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3entry Tff7b72-Tff7b72>
Te6edf3entryTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3node Tff7b72= Te6edf3findNodeByNameSuffixTb4b4b4(Te6edf3entryTb4b4b4.Te6edf3nameTb4b4b4, Te6edf3entryTb4b4b4.Te6edf3fullAddressTb4b4b4, Te6edf3nodeDbTb4b4b4, Te6edf3databaseManagerTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4.Te6edf3sortedBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3name Tb4b4b4}
T8b949e/**
* Finds a [Node] matching the last `_`-delimited segment of [displayName], if a local database exists for the given
* [fullAddress]. Used by both TCP recent-device matching and Android USB device matching to avoid duplicated
* suffix-lookup logic.
*/
Tff7b72internal Tff7b72fun Td2a8fffindNodeByNameSuffixTb4b4b4(
Te6edf3displayNameTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3fullAddressTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3nodeDbTb4b4b4: Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3NodeTff7b72>Tb4b4b4,
Te6edf3databaseManagerTb4b4b4: Te6edf3DatabaseManagerTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3Node? Tb4b4b4{
Tff7b72val Te6edf3suffix Tff7b72= Te6edf3displayNameTb4b4b4.Te6edf3splitTb4b4b4(Ta5d6ff"Ta5d6ff_Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3lastOrNullTb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3lowercaseTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Tff7b72!Te6edf3databaseManagerTb4b4b4.Te6edf3hasDatabaseForTb4b4b4(Te6edf3fullAddressTb4b4b4) Tff7b72|Tff7b72| Te6edf3suffix Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3suffixTb4b4b4.Te6edf3length Tff7b72< Te6edf3SUFFIX_LENGTHTb4b4b4) Tb4b4b4{
Tff7b72null
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3nodeDbTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3userTb4b4b4.Te6edf3idTb4b4b4.Te6edf3lowercaseTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3endsWithTb4b4b4(Te6edf3suffixTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* The virtual devices offered behind the Demo Mode gate, shared so the common and Android discovery paths cannot
* diverge on visibility policy.
*
* [showReplay] is nested inside [showMock]: replay is an extra entry *within* Demo Mode, never a device on its own. It
* additionally requires the build to carry the capture asset (`RadioTransportFactory.isReplayTransportAvailable`) —
* without it the replay transport falls back to the plain mock, so the entry would advertise something it cannot do.
*/
Tff7b72internal Tff7b72suspend Tff7b72fun Td2a8ffvirtualDeviceEntriesTb4b4b4(Te6edf3showMockTb4b4b4: Tffa657BooleanTb4b4b4, Te6edf3showReplayTb4b4b4: Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DeviceListEntryTff7b72> Tb4b4b4{
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3showMockTb4b4b4) Tff7b72return Te6edf3emptyListTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3mock Tff7b72=
Te6edf3DeviceListEntryTb4b4b4.Te6edf3MockTb4b4b4(Te6edf3safeCatchingAll Tb4b4b4{ Te6edf3getStringSuspendTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3demo_modeTb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3getOrDefaultTb4b4b4(Ta5d6ff"Ta5d6ffDemo ModeTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3replay Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3showReplayTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3label Tff7b72=
Te6edf3safeCatchingAll Tb4b4b4{ Te6edf3getStringSuspendTb4b4b4(Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3demo_mode_replayTb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3getOrDefaultTb4b4b4(Ta5d6ff"Ta5d6ffDemo Mode (Replay)Ta5d6ff"Tb4b4b4)
Te6edf3DeviceListEntryTb4b4b4.Te6edf3ReplayTb4b4b4(Te6edf3labelTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tff7b72return Te6edf3listOfNotNullTb4b4b4(Te6edf3mockTb4b4b4, Te6edf3replayTb4b4b4)
Tb4b4b4}
Served by rngit 1.5.2 - Generated in 0.08s